package com.tn.openrpc;

import com.tn.openrpc.channel.RemoteChannel;
import com.tn.openrpc.channel.RemoteChannelException;
import com.tn.openrpc.channel.RemoteChannelRequest;
import com.tn.openrpc.channel.RemoteChannelResponse;
import com.tn.openrpc.util.FactoryException;
import com.tn.openrpc.util.ParametrizedFactory;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * An implementation of <code>RemoteMethod</code> that works with a <code>RemoteChannel</code> to perform method calls.
 */
public class RemoteChannelBasedRemoteMethod implements RemoteMethod
{
  private static final Logger LOGGER = LoggerFactory.getLogger(RemoteChannelBasedRemoteMethod.class);

  private RemoteChannel remoteChannel;
  private ParametrizedFactory<? extends RemoteChannelRequest, Object[]> remoteChannelRequestFactory;

  /**
   * Creates a new <code>RemoteChannelBasedRemoteMethod</code>.
   *
   * @param remoteChannel the channel over which method invocations will be made.
   * @param target        the target of the method invocations.
   * @param methodName    the name of the method on the target.
   */
  public RemoteChannelBasedRemoteMethod(RemoteChannel remoteChannel, final String target, final String methodName)
  {
    this(
      remoteChannel,
      new ParametrizedFactory<RemoteChannelRequest, Object[]>()
      {
        @Override
        public RemoteChannelRequest newInstance(Object[] args) throws FactoryException
        {
          return new RemoteChannelRequest(target, methodName, args);
        }
      }
    );
  }

  /**
   * Creates a new <code>RemoteChannelBasedRemoteMethod</code>.
   *
   * @param remoteChannel the channel over which method invocations will be made.
   */
  public RemoteChannelBasedRemoteMethod(
    RemoteChannel remoteChannel,
    ParametrizedFactory<? extends RemoteChannelRequest, Object[]> remoteChannelRequestFactory
  )
  {
    this.remoteChannel = remoteChannel;
    this.remoteChannelRequestFactory = remoteChannelRequestFactory;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Object invoke(Object... args) throws IllegalArgumentException, RemoteMethodException
  {
    try
    {
      RemoteChannelRequest remoteChannelRequest = remoteChannelRequestFactory.newInstance(args);

      LOGGER.debug("Sending request: {} on remote channel: {}", remoteChannelRequest, remoteChannel);

      RemoteChannelResponse remoteChannelResponse = remoteChannel.send(
        remoteChannelRequest
      );

      LOGGER.debug("Received response: {} on remote channel: {}", remoteChannelRequest, remoteChannel);

      Object result = remoteChannelResponse.getResult();

      if (result instanceof IllegalArgumentException)
      {
        throw (IllegalArgumentException) result;
      }
      else if (result instanceof Throwable)
      {
        throw new RemoteInvocationTargetException((Throwable) result);
      }
      else
      {
        return result;
      }
    }
    catch (RemoteChannelException e)
    {
      throw new RemoteMethodException("An error occurred sending request over the remote channel.", e);
    }
    catch (FactoryException e)
    {
      throw new RemoteMethodException("An error occurred creating a remote channel request.", e);
    }
  }
}
